package com.xbh.wificonfiglib.task;

import android.content.Context;
import android.os.Looper;
import android.util.Log;

import com.xbh.wificonfiglib.IWifiConfigLibListener;
import com.xbh.wificonfiglib.IWifiConfigLibResult;
import com.xbh.wificonfiglib.IWifiConfigLibTask;
import com.xbh.wificonfiglib.WifiConfigLibResult;
import com.xbh.wificonfiglib.common.Constant;
import com.xbh.wificonfiglib.protocol.TouchData;
import com.xbh.wificonfiglib.protocol.WifiConfigLibGenerator;
import com.xbh.wificonfiglib.security.ITouchEncryptor;
import com.xbh.wificonfiglib.util.ByteUtil;
import com.xbh.wificonfiglib.util.SPUtil;
import com.xbh.wificonfiglib.util.SimpleCipherUtil;
import com.xbh.wificonfiglib.util.TouchNetUtil;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

public class __WifiConfigLibTaskByTCPIP implements __IWifiConfigLibTask {
    /**
     * one indivisible data contain 3 9bits info
     */
    private static final int ONE_DATA_LEN = 3;

    private static final String TAG = "__EsptouchTaskByTCP";

    private final byte[] mApSsid;
    private final byte[] mApPassword;
    private final byte[] mApBssid;
    private final ITouchEncryptor mEncryptor;
    private final Context mContext;
    private final List<IWifiConfigLibResult> mEsptouchResultList;
    private final String mIp;
    private final int mPort;
    private boolean ssidWithZh   = false;
    private volatile boolean mIsSuc = false;
    private volatile boolean mIsInterrupt = false;
    private volatile boolean mIsExecuted = false;
    private AtomicBoolean mIsCancelled;
    private IWifiConfigLibParameter mParameter;
    private volatile Map<String, Integer> mBssidTaskSucCountMap;
    private IWifiConfigLibListener mEsptouchListener;
    private Thread mTask;
    private final String apssid;
    private final String appwd;
    private IWifiConfigLibGenerator mGenerator;
    private Socket mSocket;

    public __WifiConfigLibTaskByTCPIP(Context context, TouchData apSsid, TouchData apBssid, TouchData apPassword, String ip, int port,
                                      ITouchEncryptor encryptor, IWifiConfigLibParameter parameter) {
        Log.i(TAG, "Welcome Esptouch " + IWifiConfigLibTask.ESPTOUCH_VERSION);
        mContext = context;
        mEncryptor = encryptor;
        mApSsid = apSsid.getData();
        mApPassword = apPassword.getData();
        mIp = ip;
        mPort = port;
        mApBssid = apBssid.getData();
        mIsCancelled = new AtomicBoolean(false);
        mParameter = parameter;
        mEsptouchResultList = new ArrayList<>();
        mBssidTaskSucCountMap = new HashMap<>();

        apssid = SPUtil.get(mContext, "APSSID", "null");
        appwd = SPUtil.get(mContext, "APPWD", "null");
    }

    private void __putEsptouchResult(boolean isSuc, String bssid, InetAddress inetAddress, String ssid, String psw, String macAddr) {
        synchronized (mEsptouchResultList) {
            // check whether the result receive enough UDP response
            boolean isTaskSucCountEnough = false;
            Integer count = mBssidTaskSucCountMap.get(bssid);
            if (count == null) {
                count = 0;
            }
            ++count;
            if (__IWifiConfigLibTask.DEBUG) {
                Log.d(TAG, "__putEsptouchResult(): count = " + count);
            }
            mBssidTaskSucCountMap.put(bssid, count);
            isTaskSucCountEnough = count >= mParameter
                    .getThresholdSucBroadcastCount();
            if (!isTaskSucCountEnough) {
                if (__IWifiConfigLibTask.DEBUG) {
                    Log.d(TAG, "__putEsptouchResult(): count = " + count
                            + ", isn't enough");
                }
                return;
            }
            // check whether the result is in the mEsptouchResultList already
            boolean isExist = false;
            for (IWifiConfigLibResult esptouchResultInList : mEsptouchResultList) {
                if (esptouchResultInList.getBssid().equals(bssid)) {
                    isExist = true;
                    break;
                }
            }
            // only add the result who isn't in the mEsptouchResultList
            if (!isExist) {
                final IWifiConfigLibResult esptouchResult = new WifiConfigLibResult(isSuc,
                        bssid, inetAddress, ssid, psw,macAddr);
                Log.d(TAG, "__putEsptouchResult() " + esptouchResult.toString());
                mEsptouchResultList.add(esptouchResult);
                if (mEsptouchListener != null) {
                    mEsptouchListener.onEsptouchResultAdded(esptouchResult);
                }
            }
        }
    }

    private List<IWifiConfigLibResult> __getEsptouchResultList() {
        synchronized (mEsptouchResultList) {
            if (mEsptouchResultList.isEmpty()) {
                WifiConfigLibResult esptouchResultFail = new WifiConfigLibResult(false,
                        null, null, null, null,null);
                esptouchResultFail.setIsCancelled(mIsCancelled.get());
                mEsptouchResultList.add(esptouchResultFail);
            }

            return mEsptouchResultList;
        }
    }

    private synchronized void __interrupt() {
        if (!mIsInterrupt) {
            mIsInterrupt = true;
            // interrupt the current Thread which is used to wait for udp response
            if (mTask != null) {
                mTask.interrupt();
                mTask = null;
            }

            if (mSocket != null) {
                try {
                    mSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public void setSsidWithZh(boolean ssidWithZh) {
        this.ssidWithZh = ssidWithZh;
    }

    @Override
    public void interrupt() {
        if (__IWifiConfigLibTask.DEBUG) {
            Log.d(TAG, "interrupt()");
        }
        mIsCancelled.set(true);
        __interrupt();
    }

    private void __listenAsyn(final int expectDataLen, Socket socket) {
        mTask = new Thread() {
            public void run() {
                if (__IWifiConfigLibTask.DEBUG) {
                    Log.d(TAG, "__listenAsyn() start");
                }
                long startTimestamp = System.currentTimeMillis();
//                byte[] apSsidAndPassword = ByteUtil.getBytesByString(mApSsid
//                        + mApPassword);
                byte expectOneByte = Constant.RECIVE_HEARDER;
                if (__IWifiConfigLibTask.DEBUG) {
                    Log.i(TAG, "expectOneByte: " + expectOneByte);
                }
                byte receiveOneByte = -1;
                byte[] receiveBytes = new byte[expectDataLen];
                while (mEsptouchResultList.size() < mParameter
                        .getExpectTaskResultCount() && !mIsInterrupt) {
                    try {
                        InputStream is = socket.getInputStream();
                        is.read(receiveBytes);
                        if (receiveBytes != null) {
                            receiveOneByte = receiveBytes[0];
                            //打印接收到的数据
                            Log.d(TAG, "receiveBytes：" + ByteUtil.bytes2HexStr(receiveBytes));
                        } else {
                            receiveOneByte = -1;
                        }
                        if (receiveOneByte == expectOneByte) {
                            if (__IWifiConfigLibTask.DEBUG) {
                                Log.i(TAG, "receive correct broadcast");
                            }
                            if (receiveBytes != null) {
                                byte[] bytes = new byte[receiveBytes.length - 1];
                                System.arraycopy(receiveBytes, 1, bytes, 0, bytes.length);

                                byte[] bytes1 = new byte[mGenerator.getEncryptedData().length - 1];
                                System.arraycopy(mGenerator.getEncryptedData(), 1, bytes1, 0, bytes1.length);

                                if (Arrays.equals(bytes, bytes1)) {
                                    Log.i(TAG, "receiveData verify success");
                                    __putEsptouchResult(true, null, null, new String(mGenerator.getSsid(), Charset.forName("utf-8")),
                                            new String(mGenerator.getPassword(), Charset.forName("utf-8")), null);
                                }
                                /*String bssid = ByteUtil.parseBssid(
                                        receiveBytes,
                                        mParameter.getEsptouchResultOneLen(),
                                        mParameter.getEsptouchResultMacLen());
                                InetAddress inetAddress = TouchNetUtil
                                        .parseInetAddr(
                                                receiveBytes,
                                                mParameter
                                                        .getEsptouchResultOneLen()
                                                        + mParameter
                                                        .getEsptouchResultMacLen(),
                                                mParameter
                                                        .getEsptouchResultIpLen());
                                __putEsptouchResult(true, bssid, inetAddress);*/
                            }
                        } else {
                            if (__IWifiConfigLibTask.DEBUG) {
                                Log.i(TAG, "receive rubbish message, just ignore");
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e1) {
                            e1.printStackTrace();
                        }
                    }
                }

                mIsSuc = mEsptouchResultList.size() >= mParameter
                        .getExpectTaskResultCount();
                __WifiConfigLibTaskByTCPIP.this.__interrupt();
                if (__IWifiConfigLibTask.DEBUG) {
                    Log.d(TAG, "__listenAsyn() finish");
                }
            }
        };
        mTask.start();
    }
    private void __listenSyn(final int expectDataLen, Socket socket) {
        if(socket == null){
            Log.d(TAG, "__listenSyn() return");
            return;
        }
        if (__IWifiConfigLibTask.DEBUG) {
            Log.d(TAG, "__listenSyn() start");
        }
        long startTimestamp = System.currentTimeMillis();
        byte expectOneByte = Constant.RECIVE_HEARDER;
        if (__IWifiConfigLibTask.DEBUG) {
            Log.i(TAG, "expectOneByte: " + expectOneByte);
        }
        byte receiveOneByte = -1;
        byte[] receiveBytes = new byte[expectDataLen];
        try {
            // change the socket's timeout
            int timeout = mParameter.getWaitUdpReceivingMillisecond();
            if (timeout < 0) {
                if (__IWifiConfigLibTask.DEBUG) {
                    Log.i(TAG, "esptouch timeout");
                }
                return;
            }

            Log.i(TAG, "mSocketServer's new timeout is "+ timeout + " milliseconds");
            socket.setSoTimeout(timeout);

            InputStream is = socket.getInputStream();
             is.read(receiveBytes);
            if (receiveBytes != null) {
                receiveOneByte = receiveBytes[0];
                //打印接收到的数据
                Log.d(TAG, "receiveBytes：" + ByteUtil.bytes2HexStr(receiveBytes));
            } else {
                receiveOneByte = -1;
            }
            if (receiveOneByte == expectOneByte) {
                Log.i(TAG, "receive correct data");
                int macLenght = receiveBytes[1];
                byte[] mac = new byte[macLenght];
                System.arraycopy(receiveBytes, 2, mac, 0, macLenght);

                String macAddr = ByteUtil.bytes2MacAddr(mac);
                Log.i(TAG, "receiveData verify success, mac = " + macAddr);
                __putEsptouchResult(true, null, null,
                        new String(mGenerator.getSsid(), Charset.forName("utf-8")),
                        new String(mGenerator.getPassword(), Charset.forName("utf-8")),
                        macAddr);
            } else {
                if (__IWifiConfigLibTask.DEBUG) {
                    Log.i(TAG, "receive rubbish message, just ignore");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
        }

        mIsSuc = mEsptouchResultList.size() >= mParameter
                .getExpectTaskResultCount();
        __WifiConfigLibTaskByTCPIP.this.__interrupt();
        if (__IWifiConfigLibTask.DEBUG) {
            Log.d(TAG, "__listenAsyn() finish");
        }
    }

    private void __executeSyn(IWifiConfigLibGenerator generator) {
        try {
            if(mSocket != null) {
                OutputStream os = mSocket.getOutputStream();
                byte[] data = generator.getEncryptedData();
                Log.d(TAG, "__execute data = " + ByteUtil.bytes2HexStr(data));
                os.write(data);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private boolean __execute(IWifiConfigLibGenerator generator) {

        try {
            OutputStream os = mSocket.getOutputStream();
//            while (!mIsInterrupt) {
            byte[] data = generator.getEncryptedData();
            Log.d(TAG,"__execute data = " + ByteUtil.bytes2HexStr(data));
                os.write(data);
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return mIsSuc;
    }

    private void __checkTaskValid() {
        // !!!NOTE: the esptouch task could be executed only once
        if (this.mIsExecuted) {
            throw new IllegalStateException(
                    "the Esptouch task could be executed only once");
        }
        this.mIsExecuted = true;
    }

    @Override
    public IWifiConfigLibResult executeForResult() throws RuntimeException {
        return executeForResults(1).get(0);
    }

    @Override
    public boolean isCancelled() {
        return this.mIsCancelled.get();
    }

    @Override
    public List<IWifiConfigLibResult> executeForResults(int expectTaskResultCount)
            throws RuntimeException {
        __checkTaskValid();

        mParameter.setExpectTaskResultCount(expectTaskResultCount);
        try {
            mSocket = new Socket(mIp, mPort);
            mSocket.setSoTimeout(mParameter.getWaitUdpSendingMillisecond());
        } catch (IOException e) {
            e.printStackTrace();
        }

        if(mSocket == null){
            return __getEsptouchResultList();
        }
        if (__IWifiConfigLibTask.DEBUG) {
            Log.d(TAG, "execute()");
        }
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException(
                    "Don't call the esptouch Task at Main(UI) thread directly.");
        }
        InetAddress localInetAddress = TouchNetUtil.getLocalInetAddress(mContext);
        if (__IWifiConfigLibTask.DEBUG) {
            Log.i(TAG, "localInetAddress: " + localInetAddress);
        }
        // generator the esptouch byte[][] to be transformed, which will cost
        // some time(maybe a bit much)
        mGenerator = new WifiConfigLibGenerator(mApSsid, mApBssid,
                mApPassword, ssidWithZh,localInetAddress, mEncryptor);
        // listen the esptouch result asyn
//        __listenAsyn(mParameter.getEsptouchResultTotalLen(), mSocket);
        for (int i = 0; i < mParameter.getTotalRepeatTime(); i++) {
            __executeSyn(mGenerator);
            __listenSyn(mParameter.getEsptouchResultTotalLen(), mSocket);
            if (mIsSuc) {
                return __getEsptouchResultList();
            }
        }

        if (!mIsInterrupt) {
            // wait the udp response without sending udp broadcast
            try {
                Thread.sleep(mParameter.getWaitUdpReceivingMillisecond());
            } catch (InterruptedException e) {
                // receive the udp broadcast or the user interrupt the task
                if (this.mIsSuc) {
                    return __getEsptouchResultList();
                } else {
                    this.__interrupt();
                    return __getEsptouchResultList();
                }
            }
            this.__interrupt();
        }
        SimpleCipherUtil.updateTable();
        return __getEsptouchResultList();
    }

    @Override
    public void setEsptouchListener(IWifiConfigLibListener esptouchListener) {
        mEsptouchListener = esptouchListener;
    }

}
